<route lang="json5" type="page">
  {
    style: {
      navigationBarTitleText: '我的配音',
    },
  }
</route>

<template>
  <view class="my-dubbing-container">
    <!-- 自定义导航栏
    <view class="custom-navbar">
      <view class="navbar-content">
        <view class="navbar-left" @click="goBack">
          <uni-icons type="left" size="20" color="#333"></uni-icons>
        </view>
        <view class="navbar-title">
          <text class="title-text">我的配音</text>
        </view>
        <view class="navbar-right"></view>
      </view>
    </view> -->

    <!-- 配音作品列表（按绘本聚合） -->
    <view class="dubbing-list" v-if="dubbingList.length > 0">
      <view v-for="item in dubbingList" :key="item.bookId" class="dubbing-item">
        <!-- 删除按钮 -->
        <view class="delete-btn-corner" @click="deleteDubbing(item)">
          <uni-icons type="trash" size="14" color="#fff"></uni-icons>
        </view>

        <view class="item-header" @click="viewDubbingDetail(item)">
          <view class="dubbing-cover">
            <image
              class="cover-image"
              :src="item.coverUrl"
              mode="aspectFill"
              lazy-load
              :fade-show="true"
              @load="onImageLoad"
              @error="onImageError"
            />
            <view class="cover-overlay">
              <text class="cover-title">{{ item.bookTitle.toUpperCase() }}</text>
              <view class="cover-icon">
                <uni-icons type="sound" size="16" color="#fff"></uni-icons>
              </view>
              <text class="cover-subtitle">{{ item.recordingCount }}页配音</text>
            </view>
          </view>

          <view class="dubbing-info">
            <text class="dubbing-title">{{ item.bookTitle }}</text>
            <view class="dubbing-details">
              <view class="detail-row">
                <text class="detail-label">配音进度：</text>
                <text class="detail-value progress">{{ item.recordedPages }}/{{ item.totalPages }}页 ({{ item.completionPercentage }}%)</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">总时长：</text>
                <text class="detail-value">{{ item.formattedTotalDuration }}</text>
              </view>
              <view class="detail-row" v-if="item.averageScore">
                <text class="detail-label">平均评分：</text>
                <text class="detail-value score">{{ item.averageScore }}分</text>
              </view>
              <view class="detail-row">
                <text class="detail-label">最新录制：</text>
                <text class="detail-value">{{ formatRecordTime(item.latestRecordTime) }}</text>
              </view>
            </view>
          </view>
        </view>

        <view class="dubbing-actions">
          <view class="action-btn detail-btn" @click="viewDubbingDetail(item)">
            <uni-icons type="eye" size="16" color="#333"></uni-icons>
            <text class="btn-text">详情</text>
          </view>
          <view class="action-btn share-btn" @click="shareDubbing(item)">
            <uni-icons type="redo" size="16" color="#fff"></uni-icons>
            <text class="btn-text share-text">分享</text>
          </view>
        </view>
      </view>
    </view>

    <!-- 空状态 -->
    <view class="empty-state" v-else-if="!loading">
      <view class="empty-icon">🎤</view>
      <text class="empty-title">还没有配音作品</text>
      <text class="empty-subtitle">快去录制你的第一个配音吧</text>
      <view class="empty-action">
        <view class="action-btn primary-btn" @click="goToBookshelf">
          <uni-icons type="home" size="16" color="#fff"></uni-icons>
          <text class="btn-text">去书架看看</text>
        </view>
      </view>
    </view>

    <!-- 加载状态 -->
    <view class="loading-container" v-if="loading && dubbingList.length === 0">
      <view class="loading-spinner"></view>
      <text class="loading-text">加载配音作品中...</text>
    </view>

    <!-- 分享Canvas组件 -->
    <DubbingShareCanvas
      ref="shareCanvasRef"
      :visible="showShareCanvas"
      :dubbing-data="currentShareItem"
      :user-info="userInfo"
      share-type="aggregate"
      @generated="onShareCardGenerated"
      @error="onShareCardError"
    />
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { onLoad, onShow, onShareAppMessage } from '@dcloudio/uni-app'
import {
  getUserRecordingAggregateList,
  generateBookRecordingShare,
  deleteBookRecordings
} from '@/api/recording'
import type {
  UserRecordingAggregateRespVO
} from '@/api/types/recording'
import DubbingShareCanvas from '@/components/DubbingShareCanvas.vue'

// 配音作品列表（聚合数据）
const dubbingList = ref<UserRecordingAggregateRespVO[]>([])

// 加载状态
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const pageSize = 10

// 缓存相关
const cacheKey = 'my-dubbing-cache'
const cacheExpireTime = 5 * 60 * 1000 // 5分钟缓存

// 分享相关状态
const showShareCanvas = ref(false)
const currentShareItem = ref<UserRecordingAggregateRespVO | null>(null)
const shareImageUrl = ref('')
const shareCanvasRef = ref()
const userInfo = ref({ nickname: '小配音员', avatar: '' })

// 加载配音聚合列表
const loadRecordingList = async (refresh = false) => {
  if (loading.value) return

  try {
    loading.value = true

    if (refresh) {
      currentPage.value = 1
      hasMore.value = true

      // 检查缓存
      const cachedData = getCachedData()
      if (cachedData && !refresh) {
        dubbingList.value = cachedData
        loading.value = false
        return
      }
    }

    const params = {
      pageNo: currentPage.value,
      pageSize: pageSize
    }

    const response = await getUserRecordingAggregateList(params)

    if (response.code === 0 && response.data) {
      const newList = response.data.list || []

      if (refresh) {
        dubbingList.value = newList
        // 缓存第一页数据
        if (currentPage.value === 1) {
          setCachedData(newList)
        }
      } else {
        dubbingList.value.push(...newList)
      }

      hasMore.value = newList.length === pageSize
      if (hasMore.value) {
        currentPage.value++
      }
    }
  } catch (error) {
    console.error('获取配音列表失败:', error)
    uni.showToast({
      title: '获取配音列表失败',
      icon: 'none'
    })
  } finally {
    loading.value = false
  }
}

// 图片加载成功
const onImageLoad = (e: any) => {
  console.log('✅ 封面图片加载成功:', e)
}

// 图片加载失败
const onImageError = (e: any) => {
  console.error('❌ 封面图片加载失败:', e)
}

// 返回上一页
const goBack = () => {
  uni.navigateBack({
    fail() {
      uni.reLaunch({
        url: '/pages/index/index'
      })
    }
  })
}

// 查看配音详情
const viewDubbingDetail = (item: UserRecordingAggregateRespVO) => {
  // 跳转到配音详情页
  uni.navigateTo({
    url: `/pages-sub/reading/dubbing-detail/index?bookId=${item.bookId}&title=${encodeURIComponent(item.bookTitle)}`
  })
}

// 删除配音
const deleteDubbing = async (item: UserRecordingAggregateRespVO) => {
  try {
    // 显示确认弹窗
    const result = await new Promise<boolean>((resolve) => {
      uni.showModal({
        title: '确认删除',
        content: `确定要删除《${item.bookTitle}》的所有配音记录吗？\n\n此操作将删除该绘本的所有录音文件，删除后无法恢复。`,
        confirmText: '删除',
        cancelText: '取消',
        confirmColor: '#ff4757',
        success: (res) => {
          resolve(res.confirm)
        },
        fail: () => {
          resolve(false)
        }
      })
    })

    if (!result) {
      return
    }

    // 显示删除中提示
    uni.showLoading({ title: '删除中...' })

    // 调用删除API
    const response = await deleteBookRecordings(item.bookId)

    if (response.code === 0) {
      uni.hideLoading()
      uni.showToast({
        title: '删除成功',
        icon: 'success'
      })

      // 刷新列表数据
      await loadRecordingList(true)
    } else {
      throw new Error(response.msg || '删除失败')
    }
  } catch (error) {
    uni.hideLoading()
    console.error('删除配音失败:', error)
    uni.showToast({
      title: error instanceof Error ? error.message : '删除失败',
      icon: 'none'
    })
  }
}

// 分享配音
const shareDubbing = async (item: UserRecordingAggregateRespVO) => {
  try {
    // 设置当前分享项目
    currentShareItem.value = item

    // 生成分享卡片
    if (shareCanvasRef.value) {
      uni.showLoading({ title: '生成分享卡片中...' })
      showShareCanvas.value = true
      await shareCanvasRef.value.generateShareCard()
    } else {
      // 降级到原有分享方式
      await fallbackShare(item)
    }
  } catch (error) {
    uni.hideLoading()
    console.error('分享配音失败:', error)
    uni.showToast({
      title: '分享失败',
      icon: 'none'
    })
  }
}

// 降级分享方式
const fallbackShare = async (item: UserRecordingAggregateRespVO) => {
  try {
    const response = await generateBookRecordingShare(item.bookId)
    if (response.code === 0 && response.data) {
      const shareData = response.data
      shareImageUrl.value = shareData.shareImage

      uni.showToast({
        title: '请点击右上角分享',
        icon: 'none',
        duration: 2000
      })
    }
  } catch (error) {
    console.error('获取分享信息失败:', error)
    uni.showToast({
      title: '分享失败',
      icon: 'none'
    })
  }
}

// 分享卡片生成成功回调
const onShareCardGenerated = (imageUrl: string) => {
  uni.hideLoading()
  shareImageUrl.value = imageUrl
  showShareCanvas.value = false

  uni.showToast({
    title: '请点击右上角分享',
    icon: 'none',
    duration: 2000
  })

  console.log('配音分享卡片生成成功:', imageUrl)
}

// 分享卡片生成失败回调
const onShareCardError = async (error: any) => {
  uni.hideLoading()
  showShareCanvas.value = false
  console.error('生成配音分享卡片失败:', error)

  // 降级到原有分享方式
  if (currentShareItem.value) {
    await fallbackShare(currentShareItem.value)
  }
}

// 格式化录制时间
const formatRecordTime = (timeStr: string): string => {
  const date = new Date(timeStr)
  const now = new Date()
  const diffTime = now.getTime() - date.getTime()
  const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24))

  if (diffDays === 0) {
    return '今天'
  } else if (diffDays === 1) {
    return '昨天'
  } else if (diffDays < 7) {
    return `${diffDays}天前`
  } else {
    const month = (date.getMonth() + 1).toString().padStart(2, '0')
    const day = date.getDate().toString().padStart(2, '0')
    return `${month}-${day}`
  }
}

onLoad(() => {
  console.log('我的配音页面加载完成')
  loadRecordingList(true)
})

onShow(() => {
  // 页面显示时刷新数据
  loadRecordingList(true)
})

// 跳转到书架
const goToBookshelf = () => {
  uni.switchTab({
    url: '/pages/index/index'
  })
}

// 缓存相关方法
const getCachedData = () => {
  try {
    const cached = uni.getStorageSync(cacheKey)
    if (cached && cached.timestamp && cached.data) {
      const now = Date.now()
      if (now - cached.timestamp < cacheExpireTime) {
        return cached.data
      } else {
        // 缓存过期，清除
        uni.removeStorageSync(cacheKey)
      }
    }
  } catch (error) {
    console.error('读取缓存失败:', error)
  }
  return null
}

const setCachedData = (data: UserRecordingAggregateRespVO[]) => {
  try {
    const cacheData = {
      data: data,
      timestamp: Date.now()
    }
    uni.setStorageSync(cacheKey, cacheData)
  } catch (error) {
    console.error('设置缓存失败:', error)
  }
}

// 微信分享配置
onShareAppMessage(() => {
  if (currentShareItem.value) {
    const item = currentShareItem.value
    return {
      title: `我完成了《${item.bookTitle}》的配音，共${item.recordingCount}页，快来听听吧！`,
      path: `/pages/dubbing-detail/index?bookId=${item.bookId}&shared=true`,
      imageUrl: shareImageUrl.value || item.coverUrl
    }
  }

  return {
    title: '我的配音作品集，一起来配音绘本吧！',
    path: '/pages/my-dubbing/index?shared=true',
    imageUrl: shareImageUrl.value || '/static/images/default-share.png'
  }
})
</script>

<style lang="scss" scoped>
// 导入公共样式变量
@import '@/styles/variables.scss';

.my-dubbing-container {
  background: $bg-gradient;
  min-height: 100vh;
}

/* .custom-navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  backdrop-filter: blur(10px);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
} */

/* .navbar-content {
  display: flex;
  align-items: center;
  height: 44px;
  padding: 0 16px;
}

.navbar-left {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border-radius: 50%;
  transition: all 0.3s ease;
}

.navbar-left:active {
  background-color: #f1f5f9;
  transform: scale(0.95);
}

.navbar-title {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.title-text {
  font-size: 18px;
  font-weight: 600;
  color: #1e293b;
} */

.dubbing-list {
  padding: 16px;
}

.dubbing-item {
  position: relative;
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 16px;
  padding: 24px;
  margin-bottom: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
  transition: all 0.3s ease;
  cursor: pointer;
}

.dubbing-item:active {
  transform: translateY(-2px);
  box-shadow: 0 12px 48px rgba(0, 0, 0, 0.15);
}

.item-header {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 24px;
}

.dubbing-cover {
  position: relative;
  width: 80px;
  height: 100px !important;
  border-radius: 8px;
  overflow: hidden;
  flex-shrink: 0;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.delete-btn-corner {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 32px;
  height: 32px;
  background: linear-gradient(135deg, #ff4757, #ff6b7a);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(255, 71, 87, 0.4);
  z-index: 10;
  transition: all 0.3s ease;
  cursor: pointer;
}

.delete-btn-corner:active {
  transform: scale(0.9);
  box-shadow: 0 4px 12px rgba(255, 71, 87, 0.6);
}

.cover-image {
  width: 100%;
  height: 100%;
}

.cover-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding: 8px;
}

.cover-title {
  color: #fff;
  font-size: 10px;
  font-weight: bold;
  text-align: center;
  margin-bottom: 4px;
}

.cover-icon {
  margin-bottom: 4px;
}

.cover-subtitle {
  color: #fff;
  font-size: 12px;
  font-weight: bold;
  text-align: center;
}

.dubbing-info {
  flex: 1;
}

.dubbing-title {
  display: block;
  width: 90%;
  font-size: 18px;
  font-weight: 600;
  color: #1e293b;
  margin-bottom: 12px;
}

.dubbing-details {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.detail-row {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
}

.detail-label {
  font-size: 16px;
  color: #94a3b8;
  margin-right: 8px;
}

.detail-value {
  font-size: 16px;
  color: #1e293b;
}

.detail-value.score {
  color: #ff9595;
  font-weight: 600;
}

.detail-value.progress {
  color: #42bbff;
  font-weight: 600;
}

.dubbing-actions {
  display: flex;
  gap: 16px;
  margin-top: 0;
}

.action-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px 24px;
  border-radius: 24px;
  gap: 8px;
  flex: 1;
  min-width: 100px;
  transition: all 0.3s ease;
  cursor: pointer;
}

.btn-text {
  font-size: 16px;
  font-weight: 500;
}

.detail-btn {
  background-color: #f1f5f9;
  border: 1px solid #e2e8f0;
}

.detail-btn .btn-text {
  color: #1e293b;
}

.detail-btn:active {
  transform: scale(0.95);
  background-color: #e2e8f0;
}



.share-btn {
  background: linear-gradient(135deg, #42bbff, #87ceeb);
  box-shadow: 0 4px 16px rgba(66, 187, 255, 0.3);
}

.share-text {
  color: #fff;
}

.share-btn:active {
  transform: scale(0.95);
  box-shadow: 0 6px 24px rgba(66, 187, 255, 0.4);
}

.empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding: 32px 16px;
}

.empty-icon {
  font-size: 64px;
  margin-bottom: 16px;
  animation: bounce 2s infinite;
}

.empty-title {
  font-size: 18px;
  color: #94a3b8;
  margin-bottom: 12px;
  font-weight: 600;
}

.empty-subtitle {
  font-size: 16px;
  color: #cbd5e1;
  margin-bottom: 16px;
  text-align: center;
}

.empty-action .primary-btn {
  background: linear-gradient(135deg, #42bbff, #87ceeb);
  box-shadow: 0 4px 16px rgba(66, 187, 255, 0.3);
  width: 200px;
}

.empty-action .primary-btn .btn-text {
  color: #fff;
}

.empty-action .primary-btn:active {
  transform: scale(0.95);
  box-shadow: 0 6px 24px rgba(66, 187, 255, 0.4);
}

.loading-container {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding: 32px;
  // margin-top: calc(var(--status-bar-height, 20px) + 44px);
}

.loading-spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #f1f5f9;
  border-top: 3px solid #42bbff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin-bottom: 16px;
}

.loading-text {
  font-size: 16px;
  color: #94a3b8;
}

@keyframes bounce {
  0%, 20%, 50%, 80%, 100% {
    transform: translateY(0);
  }
  40% {
    transform: translateY(-10px);
  }
  60% {
    transform: translateY(-5px);
  }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 配音作品列表 - 儿童友好风格 */
.dubbing-list {
  padding: $spacing-xl;
  // margin-top: calc(var(--status-bar-height, 20px) + 44px);

  .dubbing-item {
    position: relative;
    @include glass-effect;
    border-radius: $border-radius-xl;
    padding: $spacing-2xl;
    margin-bottom: $spacing-xl;
    box-shadow: $shadow-lg;
    transition: $transition-all;
    cursor: pointer;

    &:last-child {
      margin-bottom: 0;
    }

    &:hover {
      transform: translateY(-2px);
      box-shadow: $shadow-xl;
    }

    &:active {
      transform: scale(0.98);
    }

    .delete-btn-corner {
      position: absolute;
      top: $spacing-md;
      right: $spacing-md;
      width: 32px;
      height: 32px;
      background: linear-gradient(135deg, #ff4757, #ff6b7a);
      border-radius: 50%;
      @include center-flex;
      box-shadow: 0 2px 8px rgba(255, 71, 87, 0.4);
      z-index: 10;
      transition: $transition-all;
      cursor: pointer;

      &:active {
        transform: scale(0.9);
        box-shadow: 0 4px 12px rgba(255, 71, 87, 0.6);
      }
    }

    .item-header {
      display: flex;
      align-items: flex-start;
      gap: $spacing-xl;
      margin-bottom: $spacing-2xl;
    }

    .dubbing-cover {
      position: relative;
      width: 80px;
      height: 80px;
      border-radius: $border-radius-md;
      overflow: hidden;
      flex-shrink: 0;
      box-shadow: $shadow-md;

      .cover-image {
        width: 100%;
        height: 100%;
      }

      .cover-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        @include center-flex;
        flex-direction: column;
        padding: $spacing-sm;

        .cover-title {
          color: $white;
          font-size: $font-size-xs;
          font-weight: $font-weight-bold;
          text-align: center;
          margin-bottom: $spacing-xs;
          font-family: $font-family;
        }

        .cover-icon {
          margin-bottom: $spacing-xs;
        }

        .cover-subtitle {
          color: $white;
          font-size: $font-size-sm;
          font-weight: $font-weight-bold;
          text-align: center;
          font-family: $font-family;
        }
      }
    }

    .dubbing-info {
      flex: 1;

      .dubbing-title {
        display: block;
        font-size: $font-size-lg;
        font-weight: $font-weight-semibold;
        color: $dark-gray;
        margin-bottom: $spacing-md;
        font-family: $font-family;
      }

      .dubbing-details {
        .detail-row {
          display: flex;
          align-items: center;
          margin-bottom: $spacing-sm;

          &:last-child {
            margin-bottom: 0;
          }

          .detail-label {
            font-size: $font-size-base;
            color: $medium-gray;
            font-family: $font-family;
          }

          .detail-value {
            font-size: $font-size-base;
            color: $dark-gray;
            font-family: $font-family;

            &.score {
              color: $coral;
              font-weight: $font-weight-semibold;
            }

            &.progress {
              color: #42bbff;
              font-weight: 600;
            }
          }
        }
      }
    }
    
    .dubbing-actions {
      display: flex;
      gap: $spacing-md;
      margin-top: 0;

      .action-btn {
        flex: 1;
        @include center-flex;
        padding: $spacing-md $spacing-lg;
        border-radius: $border-radius-2xl;
        gap: $spacing-xs;
        min-width: 80px;
        transition: $transition-all;
        cursor: pointer;

        .btn-text {
          font-size: $font-size-base;
          font-weight: $font-weight-medium;
          font-family: $font-family;
        }

        &.detail-btn {
          background-color: $light-gray;
          border: 1px solid $border-color;

          .btn-text {
            color: $dark-gray;
          }

          &:active {
            transform: scale(0.95);
            background-color: $medium-gray;
          }
        }



        &.share-btn {
          background: linear-gradient(135deg, #42bbff, #87ceeb);
          box-shadow: 0 4px 16px rgba(66, 187, 255, 0.3);

          .share-text {
            color: #fff;
          }

          &:active {
            transform: scale(0.95);
            box-shadow: $shadow-blue-lg;
          }
        }
      }
    }
  }
}

/* 空状态 - 儿童友好风格 */
.empty-state {
  @include center-flex;
  flex-direction: column;
  padding: $spacing-3xl $spacing-md;
  // margin-top: calc(var(--status-bar-height, 20px) + 44px);

  .empty-icon {
    font-size: 64px;
    margin-bottom: $spacing-xl;
    animation: bounce 2s infinite;
  }

  .empty-title {
    font-size: $font-size-lg;
    color: $medium-gray;
    margin-bottom: $spacing-md;
    font-family: $font-family;
    font-weight: $font-weight-semibold;
  }

  .empty-subtitle {
    font-size: $font-size-base;
    color: $light-gray;
    font-family: $font-family;
    margin-bottom: $spacing-xl;
    text-align: center;
  }

  .empty-action {
    .primary-btn {
      background: linear-gradient(135deg, #42bbff, #87ceeb);
      box-shadow: 0 4px 16px rgba(66, 187, 255, 0.3);

      .btn-text {
        color: #fff;
      }

      &:active {
        transform: scale(0.95);
        box-shadow: $shadow-blue-lg;
      }
    }
  }
}

/* 加载状态样式 */
.loading-container {
  @include center-flex;
  flex-direction: column;
  padding: $spacing-3xl;
  // margin-top: calc(var(--status-bar-height, 20px) + 44px);

  .loading-spinner {
    width: 40px;
    height: 40px;
    border: 3px solid #f1f5f9;
    border-top: 3px solid #42bbff;
    border-radius: 50%;
    animation: spin 1s linear infinite;
    margin-bottom: 16px;
  }

  .loading-text {
    font-size: $font-size-base;
    color: $medium-gray;
    font-family: $font-family;
  }
}

/* 动画效果 */
@keyframes bounce {
  0%, 20%, 50%, 80%, 100% {
    transform: translateY(0);
  }
  40% {
    transform: translateY(-10px);
  }
  60% {
    transform: translateY(-5px);
  }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>
